<?php

class switchboard {
    public $parent = null;
    private $sb = false;
    var $myfunction = null;
    var $to_user = '';

    private $connection     = null;
    private $helper         = null;
    private $protocol       = null;

    function __construct( $ip, $port, $sid, $ticket, $parent, $email ) {
        $this->connection = &$parent->connection;
        $this->helper = &$parent->helper;
        $this->protocol = &$parent->protocol;
        $this->to_user = $email;

        $this->helper->log('SB: Opening switchboard to: '.$email);
        $this->helper->debug(sprintf('SB: try to connect to switchboard server %s:%d', $ip, $port));

        $this->sb = @fsockopen($ip, $port, $errno, $errstr, 5);

        if (!$this->sb) {
            $this->helper->debug(sprintf('SB: *** Can\'t connect to %s:%d error => %d, %s', $ip, $port, $errno, $errstr));
            $this->helper->log(sprintf('SB: *** Can\'t connect to %s:%d error => %d, %s', $ip, $port, $errno, $errstr));
            return false;
        }

        stream_set_timeout($this->sb, $this->connection->stream_timeout);
        $this->writeLn('ANS', sprintf('%s %s %s', $this->helper->user, $ticket, $sid) );
        $this->helper->debug(sprintf('SB: Switchboard opened to %s', $email));

        $this->doLoop();
        return true;
    } // end func

    function doLoop()
    {

        //False visszatérési érték jelzi a külső osztálynak,
        //hogy megszűnt a switchboard
        if ( !$this->sb ) {
            $this->helper->debug(sprintf('SB: Switchboard closed with %s', $this->to_user));
            $this->helper->log(sprintf('SB: Switchboard closed with %s', $this->to_user));
            return false;
        }

        $ignore     = false;
        $start_tm   = time();

        //Bontották a kapcsolatot
        if ( feof($this->sb) ) {
            fclose($this->sb);
            return false;
            
        }

        $data = $this->readln();
        if ($data === false) {
            return true;
            
            if ($this->parent->timeout > 0) {
                $now_tm = time();
                $used_time = ($now_tm >= $start_tm) ? $now_tm - $start_tm : $now_tm;
                if ($used_time > $this->parent->timeout) {
                    if ($ignore == false) {
                        $this->error = 'Timeout, maybe protocol changed!';
                        $this->helper->debug("*** $this->error");
                        $this->helper->log("!!! $this->error");
                    }
                    $this->writeln('OUT', '');
                    fclose($this->sb);
                    $this->sb = false;
                    return false;
                }
            }
            continue;
        }

            $code = substr($data, 0, 3);
            $start_tm = time();
            switch($code) {
                case 'IRO':
                    // SB: <<< IRO {id} {rooster} {roostercount} {email} {alias} {clientid}
                    @list(/* IRO */, /* id */, $cur_num, $total, $email, $alias, $clientid) = @explode(' ', $data);
                    $this->helper->log('*** %s join us', $email);
                    $this->helper->debug('*** %s join us', $email);
                    break;

                case 'ANS':
                    // SB: <<< ANS {id} OK
                    // ignore this
                    break;

                case 'BYE':
                    $this->helper->log("*** Quit for BYE");
                    $this->sbClose();
                    return true;

                case 'MSG':
                    // SB: <<< MSG {email} {alias} {len}
                    @list(/* MSG */, $from_email, /* alias */, $len, ) = @explode(' ', $data);
                    $len = trim($len);
                    $data = $this->readdata($len);
                    $aLines = @explode("\n", $data);
                    $header = true;
                    $ignore = false;
                    $is_p2p = false;
                    $sMsg = '';
                    foreach ($aLines as $line) {
                        $line = rtrim($line);
                        if ($header) {
                            if ($line === '') {
                                $header = false;
                                continue;
                            }
                            if (strncasecmp($line, 'TypingUser:', 11) == 0) {
                                // typing notification, just ignore
                                $ignore = true;
                                break;
                            }
                            if (strncasecmp($line, 'Chunk:', 6) == 0) {
                                // we don't handle any split message, just ignore
                                $ignore = true;
                                break;
                            }
                            if (strncasecmp($line, 'Content-Type: application/x-msnmsgrp2p', 38) == 0) {
                                // p2p message, ignore it, but we need to send acknowledgement for it...
                                $is_p2p = true;
                                $p = strstr($data, "\n\n");
                                $sMsg = '';
                                if ($p === false) {
                                    $p = strstr($data, "\r\n\r\n");
                                    if ($p !== false)
                                        $sMsg = substr($p, 4);
                                }
                                else
                                    $sMsg = substr($p, 2);
                                break;
                            }
                            if (strncasecmp($line, 'Content-Type: application/x-', 28) == 0) {
                                // ignore all application/x-... message
                                // for example:
                                //      application/x-ms-ink        => ink message
                                $ignore = true;
                                break;
                            }
                            if (strncasecmp($line, 'Content-Type: text/x-', 21) == 0) {
                                // ignore all text/x-... message
                                // for example:
                                //      text/x-msnmsgr-datacast         => nudge, voice clip....
                                //      text/x-mms-animemoticon         => customized animemotion word
                                $ignore = true;
                                break;
                            }
                            continue;
                        }
                        if ($sMsg !== '')
                            $sMsg .= "\n";
                        $sMsg .= $line;
                    }
                    if ($ignore) {
                        $this->helper->log("*** ingnore from $from_email: $line");
                        break;
                    }
                    if ($is_p2p) {
                        // we will ignore any p2p message after sending acknowledgement
                        $ignore = true;
                        $len = strlen($sMsg);
                        $this->helper->log("*** p2p message from $from_email, size $len");
                        // header = 48 bytes
                        // content >= 0 bytes
                        // footer = 4 bytes
                        // so it need to >= 52 bytes
                        if ($len < 52) {
                            $this->helper->log("*** p2p: size error, less than 52!");
                            break;
                        }
                        $aDwords = @unpack("V12dword", $sMsg);
                        if (!is_array($aDwords)) {
                            $this->helper->log("*** p2p: header unpack error!");
                            break;
                        }
                        $this->helper->debug("*** p2p: dump received message:\n".$this->dump_binary($sMsg));
                        $hdr_SessionID = $aDwords['dword1'];
                        $hdr_Identifier = $aDwords['dword2'];
                        $hdr_DataOffsetLow = $aDwords['dword3'];
                        $hdr_DataOffsetHigh = $aDwords['dword4'];
                        $hdr_TotalDataSizeLow = $aDwords['dword5'];
                        $hdr_TotalDataSizeHigh = $aDwords['dword6'];
                        $hdr_MessageLength = $aDwords['dword7'];
                        $hdr_Flag = $aDwords['dword8'];
                        $hdr_AckID = $aDwords['dword9'];
                        $hdr_AckUID = $aDwords['dword10'];
                        $hdr_AckSizeLow = $aDwords['dword11'];
                        $hdr_AckSizeHigh = $aDwords['dword12'];
                        $this->helper->debug("*** p2p: header SessionID = $hdr_SessionID");
                        $this->helper->debug("*** p2p: header Inentifier = $hdr_Identifier");
                        $this->helper->debug("*** p2p: header Data Offset Low = $hdr_DataOffsetLow");
                        $this->helper->debug("*** p2p: header Data Offset High = $hdr_DataOffsetHigh");
                        $this->helper->debug("*** p2p: header Total Data Size Low = $hdr_TotalDataSizeLow");
                        $this->helper->debug("*** p2p: header Total Data Size High = $hdr_TotalDataSizeHigh");
                        $this->helper->debug("*** p2p: header MessageLength = $hdr_MessageLength");
                        $this->helper->debug("*** p2p: header Flag = $hdr_Flag");
                        $this->helper->debug("*** p2p: header AckID = $hdr_AckID");
                        $this->helper->debug("*** p2p: header AckUID = $hdr_AckUID");
                        $this->helper->debug("*** p2p: header AckSize Low = $hdr_AckSizeLow");
                        $this->helper->debug("*** p2p: header AckSize High = $hdr_AckSizeHigh");

                        if ($hdr_Flag == 2) {
                            // just send ACK...
                            $this->writeln("ACK $this->parent->trans_id");
                            break;
                        }
                        if ($hdr_SessionID == 4 && $hdr_Flag == 2) {
                            // ignore?
                            $this->helper->debug("*** p2p: ignore flag 4");
                            break;
                        }
                        $finished = false;
                        if ($hdr_TotalDataSizeHigh == 0) {
                            // only 32 bites size
                            if (($hdr_MessageLength + $hdr_DataOffsetLow) == $hdr_TotalDataSizeLow)
                                $finished = true;
                        }
                        else {
                            // we won't accept any file transfer
                            // so I think we won't get any message size need to use 64 bits
                            // 64 bits size here, can't count directly...
                            $totalsize = base_convert(sprintf("%X%08X", $hdr_TotalDataSizeHigh, $hdr_TotalDataSizeLow), 16, 10);
                            $dataoffset = base_convert(sprintf("%X%08X", $hdr_DataOffsetHigh, $hdr_DataOffsetLow), 16, 10);
                            $messagelength = base_convert(sprintf("%X", $hdr_MessageLength), 16, 10);
                            $now_size = bcadd($dataoffset, $messagelength);
                            if (bccomp($now_size, $totalsize) >= 0)
                                $finished = true;
                        }
                        if (!$finished) {
                            // ignore not finished split packet
                            $this->helper->debug("*** p2p: ignore split packet, not finished");
                            break;
                        }
                        $new_id = ~$hdr_Identifier;
                        $hdr = pack("LLLLLLLLLLLL", $hdr_SessionID,
                                                    $new_id,
                                                    0, 0,
                                                    $hdr_TotalDataSizeLow, $hdr_TotalDataSizeHigh,
                                                    0,
                                                    2,
                                                    $hdr_Identifier,
                                                    $hdr_AckID,
                                                    $hdr_TotalDataSizeLow, $hdr_TotalDataSizeHigh);
                        $footer = pack("L", 0);
                        $message = "MIME-Version: 1.0\r\nContent-Type: application/x-msnmsgrp2p\r\nP2P-Dest: $from_email\r\n\r\n$hdr$footer";
                        $len = strlen($message);
                        $this->writeln("MSG 1 D $len");
                        $this->writedata($message);
                        $this->helper->log("*** p2p: send acknowledgement for $hdr_SessionID");
                        $this->helper->debug("*** p2p: dump sent message:\n".$this->dump_binary($hdr.$footer));
                        break;
                    }
                    $this->helper->log("*** MSG from $from_email: $sMsg");
//                    if ($my_function && function_exists($my_function) && is_callable($my_function)) {
//                        $reply_msg = $my_function($from_email, $sMsg, &$this);
                        $reply_msg = 'burp';
                        if ($reply_msg !== '') {
                            /* typing */
                            $message = "MIME-Version: 1.0\r\nContent-Type: text/x-msmsgscontrol\r\nTypingUser: ".$this->parent->user."\r\n\r\n\r\n";
                            $len = strlen($message);
                            $this->writeln("MSG 20 N $len");
                            $this->writedata($message);
//                            sleep(2);
                            
                            $aMessage = $this->helper->getMessage($reply_msg);
                            foreach ($aMessage as $message) {
                                $len = strlen($message);
                                $this->writeln("MSG 20 N $len");
                                $this->writedata($message);
                            }
                            $this->helper->log("Response to $from_email:\n$reply_msg");
                        }
//                    }
/*
                    $this->writeln("OUT");
                    fclose($this->sb);
                    $this->sb = false;
*/
                    return true;

                default:
                    if (is_numeric($code)) {
                        $this->error = "Error code: $code, please check the detail information from: http://msnpiki.msnfanatic.com/index.php/Reference:Error_List";
                        $this->helper->debug("*** SB: $this->error");
                    } else {
                        $this->helper->debug($data);
                    }
                    break;
            }
//        }        

        $this->helper->debug('SB: EOF');
    } // end func


    function sendMessage( $reply_msg )
    {
        echo 'SendMessage';
            $aMessage = $this->helper->getMessage($reply_msg);
            foreach ($aMessage as $message) {
                $len = strlen($message);
                $this->writeln("MSG 20 N $len");
                $this->writedata($message);
            }
            $this->helper->log("Response\n$reply_msg");
        
    } // end func


    function close() {
        $this->writeln("OUT");
        fclose($this->sb);
        $this->sb = false;
    } // end func

    // read data for specified size for SB
    function readData($size)
    {
        $data = '';
        $count = 0;
        while (!feof($this->sb)) {
            $buf = @fread($this->sb, $size - $count);
            $data .= $buf;
            $count += strlen($buf);
            if ($count >= $size) break;
        }
        $this->helper->debug("SB: data ($size/$count) <<<\n$data");
        return $data;
    }

    // read one line for SB
    function readLn()
    {
        $data = @fgets($this->sb, 4096);
        if ($data !== false) {
            $data = trim($data);
            $this->helper->debug("SB: <<< $data");
        }
        return $data;
    }

    // write to server for SB, append \r\n, also increase id
    // switchboard server only accept \r\n, it will lost connection if just \n only
    function writeLn($cmd, $data)
    {
        $fullCmd = sprintf( '%s %d %s', $cmd, $this->connection->trans_id, $data );

        @fwrite($this->sb, $fullCmd."\r\n");
        $this->helper->debug('SB: >>> '.$data);
        $this->connection->trans_id++;
        return;
    }

    // write data to server
    function writeData($data)
    {
        @fwrite($this->sb, $data);
        $this->helper->debug("SB: >>> $data");
        return;
    }
} // end class
 
?>